# uncompyle6 version 3.2.3
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.8 |Anaconda custom (64-bit)| (default, Feb 21 2019, 18:30:04) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: site-packages\PyInstaller\building\build_main.py
"""
Build packages using spec files.

NOTE: All global variables, classes and imported modules create API
      for .spec files.
"""
import glob, os, pprint, shutil, sys
from .. import HOMEPATH, DEFAULT_DISTPATH, DEFAULT_WORKPATH
from .. import compat
from .. import log as logging
from ..utils.misc import absnormpath
from ..compat import is_py2, is_win, PYDYLIB_NAMES, VALID_MODULE_TYPES
from ..depend import bindepend
from ..depend.analysis import initialize_modgraph
from .api import PYZ, EXE, COLLECT, MERGE
from .datastruct import TOC, Target, Tree, _check_guts_eq
from .imphook import AdditionalFilesCache, ModuleHookCache
from .osx import BUNDLE
from .toc_conversion import DependencyProcessor
from .utils import _check_guts_toc_mtime, format_binaries_and_datas
from ..depend.utils import create_py3_base_library, scan_code_for_ctypes
from ..archive import pyz_crypto
from ..utils.misc import get_path_to_toplevel_modules, get_unicode_modules, mtime
from ..configure import get_importhooks_dir

if is_win:
    from ..utils.win32 import winmanifest
logger = logging.getLogger(__name__)
STRINGTYPE = type("")
TUPLETYPE = type((None,))
rthooks = {}
_init_code_path = os.path.join(HOMEPATH, "PyInstaller", "loader")


def _old_api_error(obj_name):
    """
    Cause PyInstall to exit when .spec file uses old api.
    :param obj_name: Name of the old api that is no longer suppored.
    """
    raise SystemExit(
        "%s has been removed in PyInstaller 2.0. Please update your spec-file. See http://www.pyinstaller.org/wiki/MigrateTo2.0 for details"
        % obj_name
    )


def setupUPXFlags():
    f = compat.getenv("UPX", "")
    if is_win:
        f = "--strip-loadconf " + f
    f = "--compress-icons=0 " + f
    f = "--best " + f
    compat.setenv("UPX", f)


class Analysis(Target):
    r"""
    Class does analysis of the user's main Python scripts.
    
    An Analysis has five outputs, all TOCs (Table of Contents) accessed as
    attributes of the analysis.
    
    scripts
            The scripts you gave Analysis as input, with any runtime hook scripts
            prepended.
    pure
            The pure Python modules.
    binaries
            The extensionmodules and their dependencies. The secondary dependecies
            are filtered. On Windows files from C:\Windows are excluded by default.
            On Linux/Unix only system libraries from /lib or /usr/lib are excluded.
    datas
            Data-file dependencies. These are data-file that are found to be needed
            by modules. They can be anything: plugins, font files, images, translations,
            etc.
    zipfiles
            The zipfiles dependencies (usually .egg files).
    """
    _old_scripts = set(
        (
            absnormpath(os.path.join(HOMEPATH, "support", "_mountzlib.py")),
            absnormpath(os.path.join(HOMEPATH, "support", "useUnicode.py")),
            absnormpath(os.path.join(HOMEPATH, "support", "useTK.py")),
            absnormpath(os.path.join(HOMEPATH, "support", "unpackTK.py")),
            absnormpath(os.path.join(HOMEPATH, "support", "removeTK.py")),
        )
    )

    def __init__(
        self,
        scripts,
        pathex=None,
        binaries=None,
        datas=None,
        hiddenimports=None,
        hookspath=None,
        excludes=None,
        runtime_hooks=None,
        cipher=None,
        win_no_prefer_redirects=False,
        win_private_assemblies=False,
    ):
        """
        scripts
                A list of scripts specified as file names.
        pathex
                An optional list of paths to be searched before sys.path.
        binaries
                An optional list of additional binaries (dlls, etc.) to include.
        datas
                An optional list of additional data files to include.
        hiddenimport
                An optional list of additional (hidden) modules to include.
        hookspath
                An optional list of additional paths to search for hooks.
                (hook-modules).
        excludes
                An optional list of module or package names (their Python names,
                not path names) that will be ignored (as though they were not found).
        runtime_hooks
                An optional list of scripts to use as users' runtime hooks. Specified
                as file names.
        win_no_prefer_redirects
                If True, prefers not to follow version redirects when searching for
                Windows SxS Assemblies.
        win_private_assemblies
                If True, changes all bundled Windows SxS Assemblies into Private
                Assemblies to enforce assembly versions.
        
        """
        super(Analysis, self).__init__()
        from ..config import CONF

        self.inputs = []
        spec_dir = os.path.dirname(CONF["spec"])
        for script in scripts:
            if not os.path.isabs(script):
                script = os.path.join(spec_dir, script)
            if absnormpath(script) in self._old_scripts:
                logger.warning("Ignoring obsolete auto-added script %s", script)
                continue
                script = os.path.normpath(script)
                if not os.path.exists(script):
                    raise ValueError("script '%s' not found" % script)
                self.inputs.append(script)

        CONF["main_script"] = self.inputs[0]
        self.pathex = self._extend_pathex(pathex, self.inputs)
        CONF["pathex"] = self.pathex
        logger.info("Extending PYTHONPATH with paths\n" + pprint.pformat(self.pathex))
        sys.path.extend(self.pathex)
        CONF["binding_redirects"] = []
        self.hiddenimports = hiddenimports or []
        self.hiddenimports.extend(CONF["hiddenimports"])
        self.hookspath = hookspath
        self.custom_runtime_hooks = runtime_hooks or []
        if cipher:
            logger.info("Will encrypt Python bytecode with key: %s", cipher.key)
            pyi_crypto_key_path = os.path.join(
                CONF["workpath"], "pyimod00_crypto_key.py"
            )
            with open(pyi_crypto_key_path, "w") as (f):
                f.write("key = %r\n" % cipher.key)
            logger.info("Adding dependencies on pyi_crypto.py module")
            self.hiddenimports.append(pyz_crypto.get_crypto_hiddenimports())
        self.excludes = excludes or []
        self.scripts = TOC()
        self.pure = TOC()
        self.binaries = TOC()
        self.zipfiles = TOC()
        self.zipped_data = TOC()
        self.datas = TOC()
        self.dependencies = TOC()
        self.binding_redirects = CONF["binding_redirects"] = []
        self.win_no_prefer_redirects = win_no_prefer_redirects
        self.win_private_assemblies = win_private_assemblies
        self._python_version = sys.version
        self.__postinit__()
        if binaries:
            logger.info("Appending 'binaries' from .spec")
            for name, pth in format_binaries_and_datas(binaries, workingdir=spec_dir):
                self.binaries.append((name, pth, "BINARY"))

        if datas:
            logger.info("Appending 'datas' from .spec")
            for name, pth in format_binaries_and_datas(datas, workingdir=spec_dir):
                self.binaries.append((name, pth, "DATA"))

    _GUTS = (
        ("inputs", _check_guts_eq),
        ("pathex", _check_guts_eq),
        ("hiddenimports", _check_guts_eq),
        ("hookspath", _check_guts_eq),
        ("excludes", _check_guts_eq),
        ("custom_runtime_hooks", _check_guts_eq),
        ("win_no_prefer_redirects", _check_guts_eq),
        ("win_private_assemblies", _check_guts_eq),
        ("_python_version", _check_guts_eq),
        ("scripts", _check_guts_toc_mtime),
        ("pure", lambda *args: (_check_guts_toc_mtime(*args, **{"pyc": 1}))),
        ("binaries", _check_guts_toc_mtime),
        ("zipfiles", _check_guts_toc_mtime),
        ("zipped_data", None),
        ("datas", _check_guts_toc_mtime),
        ("binding_redirects", None),
    )

    def _extend_pathex(self, spec_pathex, scripts):
        """
        Normalize additional paths where PyInstaller will look for modules and
        add paths with scripts to the list of paths.
        
        :param spec_pathex: Additional paths defined defined in .spec file.
        :param scripts: Scripts to create executable from.
        :return: list of updated paths
        """
        pathex = []
        for script in scripts:
            logger.debug("script: %s" % script)
            script_toplevel_dir = get_path_to_toplevel_modules(script)
            if script_toplevel_dir:
                pathex.append(script_toplevel_dir)

        if spec_pathex is not None:
            pathex.extend(spec_pathex)
        return [absnormpath(p) for p in pathex]

    def _check_guts(self, data, last_build):
        if Target._check_guts(self, data, last_build):
            return True
        else:
            for fnm in self.inputs:
                if mtime(fnm) > last_build:
                    logger.info("Building because %s changed", fnm)
                    return True

            self.scripts = TOC(data["scripts"])
            self.pure = TOC(data["pure"])
            self.binaries = TOC(data["binaries"])
            self.zipfiles = TOC(data["zipfiles"])
            self.zipped_data = TOC(data["zipped_data"])
            self.datas = TOC(data["datas"])
            from ..config import CONF

            self.binding_redirects = CONF["binding_redirects"] = data[
                "binding_redirects"
            ]
            return False

    def assemble(self):
        """
        This method is the MAIN method for finding all necessary files to be bundled.
        """
        from ..config import CONF

        if "tests_modgraph" in CONF:
            if not self.excludes:
                logger.info("Reusing basic module graph object.")
                self.graph = CONF["tests_modgraph"]
            for m in self.excludes:
                logger.debug("Excluding module '%s'" % m)

            self.graph = initialize_modgraph(
                excludes=self.excludes, user_hook_dirs=self.hookspath
            )
        if not is_py2:
            libzip_filename = os.path.join(CONF["workpath"], "base_library.zip")
            create_py3_base_library(libzip_filename, graph=self.graph)
            self.datas.append(
                (os.path.basename(libzip_filename), libzip_filename, "DATA")
            )
        self.graph.path = self.pathex + self.graph.path
        self.graph.set_setuptools_nspackages()
        self.graph.add_hiddenimports(self.hiddenimports)
        logger.info("running Analysis %s", self.tocbasename)
        python = sys.executable
        if not is_win:
            while os.path.islink(python):
                python = os.path.join(os.path.dirname(python), os.readlink(python))

            depmanifest = None
        else:
            depmanifest = winmanifest.Manifest(
                type_="win32",
                name=CONF["specnm"],
                processorArchitecture=winmanifest.processor_architecture(),
                version=(1, 0, 0, 0),
            )
            depmanifest.filename = os.path.join(
                CONF["workpath"], CONF["specnm"] + ".exe.manifest"
            )
        bindepend.seen.clear()
        self.binaries.extend(
            (
                bindepend.Dependencies(
                    [("", python, "")],
                    manifest=depmanifest,
                    redirects=self.binding_redirects,
                )
            )[1:]
        )
        if is_win:
            depmanifest.writeprettyxml()
        logger.info("Caching module hooks...")
        module_hook_dirs = [get_importhooks_dir()]
        if self.hookspath:
            module_hook_dirs.extend(self.hookspath)
        module_hook_cache = ModuleHookCache(
            module_graph=self.graph, hook_dirs=module_hook_dirs
        )
        priority_scripts = []
        for script in self.inputs:
            logger.info("Analyzing %s", script)
            priority_scripts.append(self.graph.run_script(script))

        logger.info("Loading module hooks...")
        additional_files_cache = AdditionalFilesCache()
        while 1:
            hooked_module_names = set()
            for module_name, module_hooks in module_hook_cache.items():
                module_node = self.graph.findNode(module_name, create_nspkg=False)
                if module_node is None:
                    continue
                if type(module_node).__name__ not in VALID_MODULE_TYPES:
                    hooked_module_names.add(module_name)
                    continue
                for module_hook in module_hooks:
                    module_hook.post_graph()
                    additional_files_cache.add(
                        module_name, module_hook.binaries, module_hook.datas
                    )

                hooked_module_names.add(module_name)

            module_hook_cache.remove_modules(*hooked_module_names)
            if not hooked_module_names:
                break

        deps_proc = DependencyProcessor(self.graph, additional_files_cache)
        self.binaries.extend(deps_proc.make_binaries_toc())
        self.datas.extend(deps_proc.make_datas_toc())
        self.zipped_data.extend(deps_proc.make_zipped_data_toc())
        logger.info("Looking for ctypes DLLs")
        ctypes_code_objs = self.graph.get_co_using_ctypes()
        for name, co in ctypes_code_objs.items():
            logger.debug("Scanning %s for shared libraries or dlls", name)
            ctypes_binaries = scan_code_for_ctypes(co)
            self.binaries.extend(set(ctypes_binaries))

        priority_scripts = (
            self.graph.analyze_runtime_hooks(self.custom_runtime_hooks)
            + priority_scripts
        )
        self.scripts = self.graph.nodes_to_toc(priority_scripts)
        self.binaries = self.graph.make_binaries_toc(self.binaries)
        if not len(self.pure) == 0:
            raise AssertionError
        self.pure = self.graph.make_pure_toc()
        self.pure._code_cache = self.graph.get_code_objects()
        logger.info("Looking for dynamic libraries")
        self.binaries.extend(
            bindepend.Dependencies(self.binaries, redirects=self.binding_redirects)
        )
        logger.info("Looking for eggs")
        self.zipfiles.extend(deps_proc.make_zipfiles_toc())
        self._check_python_library(self.binaries)
        if is_win:
            self.binding_redirects[:] = list(set(self.binding_redirects))
            logger.info("Found binding redirects: \n%s", self.binding_redirects)
        self._write_warnings()
        self._write_graph_debug()

    def _write_warnings(self):
        """
        Write warnings about missing modules. Get them from the graph
        and use the graph to figure out who tried to import them.
        """
        from ..config import CONF

        miss_toc = self.graph.make_missing_toc()
        if len(miss_toc):
            wf = open(CONF["warnfile"], "w")
            for n, p, status in miss_toc:
                importer_names = self.graph.importer_names(n)
                wf.write(
                    status
                    + " module named "
                    + n
                    + " - imported by "
                    + (", ").join(importer_names)
                    + "\n"
                )

            wf.close()
            logger.info("Warnings written to %s", CONF["warnfile"])

    def _write_graph_debug(self):
        """Write a xref (in html) and with `--log-level DEBUG` a dot-drawing
        of the graph.
        """
        from ..config import CONF

        with open(CONF["xref-file"], "w") as (fh):
            self.graph.create_xref(fh)
            logger.info("Graph cross-reference written to %s", CONF["xref-file"])
        if logger.getEffectiveLevel() > logging.DEBUG:
            return
        with open(CONF["dot-file"], "w") as (fh):
            self.graph.graphreport(fh)
            logger.info("Graph drawing written to %s", CONF["dot-file"])

    def _check_python_library(self, binaries):
        """
        Verify presence of the Python dynamic library in the binary dependencies.
        Python library is an essential piece that has to be always included.
        """
        for nm, filename, typ in binaries:
            if typ == "BINARY":
                if nm in PYDYLIB_NAMES:
                    logger.info("Using Python library %s", filename)
                    return

        logger.info(
            "Python library not in binary dependencies. Doing additional searching..."
        )
        python_lib = bindepend.get_python_library_path()
        if python_lib:
            logger.debug("Adding Python library to binary dependencies")
            binaries.append((os.path.basename(python_lib), python_lib, "BINARY"))
            logger.info("Using Python library %s", python_lib)
        else:
            msg = (
                "Python library not found: %s\nThis would mean your Python installation doesn't come with proper library files.\nThis usually happens by missing development package, or unsuitable build parameters of Python installation.\n\n* On Debian/Ubuntu, you would need to install Python development packages\n  * apt-get install python3-dev\n  * apt-get install python-dev\n* If you're building Python by yourself, please rebuild your Python with `--enable-shared` (or, `--enable-framework` on Darwin)\n"
                % ((", ").join(PYDYLIB_NAMES),)
            )
            raise IOError(msg)


class ExecutableBuilder(object):
    """
    Class that constructs the executable.
    """

    pass


def build(spec, distpath, workpath, clean_build):
    """
    Build the executable according to the created SPEC file.
    """
    from ..config import CONF

    def TkPKG(*args, **kwargs):
        global _old_api_error
        _old_api_error("TkPKG")

    def TkTree(*args, **kwargs):
        _old_api_error("TkTree")

    distpath = compat.expand_path(distpath)
    workpath = compat.expand_path(workpath)
    CONF["spec"] = compat.expand_path(spec)
    CONF["specpath"], CONF["specnm"] = os.path.split(spec)
    CONF["specnm"] = os.path.splitext(CONF["specnm"])[0]
    if os.path.dirname(distpath) == HOMEPATH:
        distpath = os.path.join(HOMEPATH, CONF["specnm"], os.path.basename(distpath))
    CONF["distpath"] = distpath
    if os.path.dirname(workpath) == HOMEPATH:
        workpath = os.path.join(
            HOMEPATH, CONF["specnm"], os.path.basename(workpath), CONF["specnm"]
        )
    else:
        workpath = os.path.join(workpath, CONF["specnm"])
    CONF["warnfile"] = os.path.join(workpath, "warn%s.txt" % CONF["specnm"])
    CONF["dot-file"] = os.path.join(workpath, "graph-%s.dot" % CONF["specnm"])
    CONF["xref-file"] = os.path.join(workpath, "xref-%s.html" % CONF["specnm"])
    if clean_build:
        logger.info(
            "Removing temporary files and cleaning cache in %s", CONF["cachedir"]
        )
        for pth in (CONF["cachedir"], workpath):
            if os.path.exists(pth):
                for f in glob.glob(pth + "/*"):
                    if os.path.isdir(f):
                        shutil.rmtree(f)
                    else:
                        os.remove(f)

    for pth in (CONF["distpath"], workpath):
        if not os.path.exists(pth):
            os.makedirs(pth)

    spec_namespace = {
        "DISTPATH": CONF["distpath"],
        "HOMEPATH": HOMEPATH,
        "SPEC": CONF["spec"],
        "specnm": CONF["specnm"],
        "SPECPATH": CONF["specpath"],
        "WARNFILE": CONF["warnfile"],
        "workpath": workpath,
        "TOC": TOC,
        "Analysis": Analysis,
        "BUNDLE": BUNDLE,
        "COLLECT": COLLECT,
        "EXE": EXE,
        "MERGE": MERGE,
        "PYZ": PYZ,
        "Tree": Tree,
        "TkPKG": TkPKG,
        "TkTree": TkTree,
        "os": os,
        "pyi_crypto": pyz_crypto,
    }
    from ..config import CONF

    CONF["workpath"] = workpath
    with open(spec, "r") as (f):
        text = f.read()
    exec(text, spec_namespace)


def __add_options(parser):
    parser.add_argument(
        "--distpath",
        metavar="DIR",
        default=DEFAULT_DISTPATH,
        help="Where to put the bundled app (default: %s)"
        % os.path.join(os.curdir, "dist"),
    )
    parser.add_argument(
        "--workpath",
        default=DEFAULT_WORKPATH,
        help="Where to put all the temporary work files, .log, .pyz and etc. (default: %s)"
        % os.path.join(os.curdir, "build"),
    )
    parser.add_argument(
        "-y",
        "--noconfirm",
        action="store_true",
        default=False,
        help="Replace output directory (default: %s) without asking for confirmation"
        % os.path.join("SPECPATH", "dist", "SPECNAME"),
    )
    parser.add_argument(
        "--upx-dir",
        default=None,
        help="Path to UPX utility (default: search the execution path)",
    )
    parser.add_argument(
        "-a",
        "--ascii",
        action="store_true",
        help="Do not include unicode encoding support (default: included if available)",
    )
    parser.add_argument(
        "--clean",
        dest="clean_build",
        action="store_true",
        default=False,
        help="Clean PyInstaller cache and remove temporary files before building.",
    )


def main(pyi_config, specfile, noconfirm, ascii=False, **kw):
    from ..config import CONF

    CONF["noconfirm"] = noconfirm
    if CONF.get("hiddenimports") is None:
        CONF["hiddenimports"] = []
    if not ascii:
        CONF["hiddenimports"].extend(get_unicode_modules())
    if pyi_config is None:
        import PyInstaller.configure as configure

        CONF.update(configure.get_config(kw.get("upx_dir")))
    else:
        CONF.update(pyi_config)
    if CONF["hasUPX"]:
        setupUPXFlags()
    CONF["ui_admin"] = kw.get("ui_admin", False)
    CONF["ui_access"] = kw.get("ui_uiaccess", False)
    build(specfile, kw.get("distpath"), kw.get("workpath"), kw.get("clean_build"))
